// Bachelor of Software Engineering 
// Media Design School 
// Auckland
// New Zealand
// 
// (c) 2005 - 2014 Media Design School 
// 
// File Name : Game.cpp
// Description : Game implementation
// Author : Serge Radinovich
// Mail : srad015@hotmail.com

#pragma once
#include "Game.h"
#include <assert.h>

//Singleton
Game* Game::pGame = nullptr;

////////////////////////////
//Restart the game variables
///////////////////////////
void Game::Reinitialize()
{
	//SCORE
	Score = 0;
	MovesTaken = 0;
	m_pTime = new Time();
	TimeInSeconds = 0;
	LastTime = 0;
}

////////////////////////////
//Constructor
///////////////////////////
Game::Game(HWND _hWnd, HINSTANCE _hInstance, HDC _hDC) : m_UndoButton(215, 180, 72, 29), m_bInitializing(true), m_bGameOver(false), m_bButtonPushed(false)
{
	
	//SCORE
	Score = 0;
	MovesTaken = 0;
	m_pTime = new Time();
	TimeInSeconds = 0;
	LastTime = 0;

	Win32GameEngine::m_hInstance = _hInstance;
	Win32GameEngine::m_hWnd = _hWnd;
	Win32GameEngine::m_hDC = _hDC;
	//Win32GameEngine::m_pTime = _pTime;


	m_UndoButton.SetHDC(InitializeBitmap(IDB_UNDOBUTTON), InitializeBitmap(IDB_UNDOBUTTON2));

	m_pBackBuffer = new BackBuffer(_hWnd, SCREENXMAX, SCREENYMAX);
	m_pBackBuffer->SetBackgroundDC(InitializeBitmap(IDB_BACKGROUND));

	//Cards
	for(int i = 0; i < 4; i++)
	{
		for(int j = 0; j < 13; j++)
		{
			//Add cards to vector
			m_Cards.push_back(new Card((ERANK)j, (ESUIT)i));
			//Create bitmaps for each card using IDB_CARDS52
			HDC _IndividualCard = InitializeBitmap(IDB_EMPTYCARD);
			HDC _Cards52DC = InitializeBitmap(IDB_CARDS52);
			//Draw individual card from bitmap of 52
			BitBlt(_IndividualCard, 0, 0, Card::s_kiWidth, Card::s_kiHeight, _Cards52DC, Card::s_kiWidth * j,  Card::s_kiHeight * i, SRCAND);
			//Assign invidivual card to newly created Card object
			HDC _cardDCs[2];
			_cardDCs[0] = _IndividualCard;
			_cardDCs[1] = InitializeBitmap(IDB_BACKCARD);
			m_Cards.back()->SetHDC(_cardDCs, 2);
						
		}
	}
	std::random_shuffle(m_Cards.begin(), m_Cards.end());

	//Todo erase memory from m_Cards
	//Allocate cards to Tableau
	std::list<Card*> _CardsList;
	auto _cardIter = m_Cards.begin();
	while(_cardIter != m_Cards.end())
	{
		_CardsList.push_back(new Card(*(*_cardIter)));
		++_cardIter;
	}
	//std::copy(m_Cards.begin(), m_Cards.end(), _CardsList);
	m_pTableau = new Tableau(&_CardsList);
	m_pTableau->SetNullSpaceDC(InitializeBitmap(IDB_NULLSPACE));
	m_pStock = new Stock(&_CardsList);
	m_pStock->SetNullSpaceDC(InitializeBitmap(IDB_NULLSPACE));
	m_pFoundation = new Foundation();
	m_pFoundation->SetNullSpaceDC(InitializeBitmap(IDB_NULLSPACE));
	hbrush = CreateSolidBrush(RGB(27,91,0));
}

////////////////////////////
//Destructor
///////////////////////////
Game::~Game()
{
	pGame = 0;
	delete m_pBackBuffer;
	delete m_pTime;
	delete m_pFoundation;
	delete m_pTableau;
	delete m_pStock;

	m_pFoundation = nullptr;
	m_pTableau = nullptr;
	m_pStock = nullptr;

	while(!m_Moves.empty())
	{
		delete m_Moves.top();
		m_Moves.pop();
	}
	while(!m_Cards.empty())
	{
		delete m_Cards.back();
		m_Cards.pop_back();
	}
	while(!m_CardsGrabbed.empty())
	{
		delete m_CardsGrabbed.back();
		m_CardsGrabbed.pop_back();
	}
}

////////////////////////////
//Instantiates the game or returns a pointer to it
///////////////////////////
Game* Game::CreateGame(HWND _hWnd, HINSTANCE _hInstance, HDC _hDC)
{	
	if(!Game::pGame)
	{
		pGame = new Game(_hWnd, _hInstance, _hDC);
	}
	
	return Game::pGame;
}

////////////////////////////
//Returns a base class pointer to one of the piles in the game based on the position of the mouse click
//Tableau, Stock or Foundation. Or nullptr
///////////////////////////
CardPileGroup* Game::IsolateCardPileGroup(Vector2 _clickPos)
{
	//Click is on left, at stock
	if(_clickPos.x <= Stock::kiFreeLeft + Card::s_kiWidth && _clickPos.y <=Stock::s_kiTop +  Card::s_kiHeight + 3*(Stock::kiDistBetweenCards))
	{
		return m_pStock;
	}
	else
	{
		//Click is below middle, at tableau
		if(_clickPos.y >= Tableau::s_kiTop)
		{
			return m_pTableau;
		}
		else 
		{
			return m_pFoundation;
		}
	}

	return nullptr;

}

////////////////////////////
//Draws the game every frame
///////////////////////////
void Game::Draw()
{
	//Score initialization
	static wchar_t ScoreDisplay[256];
	static wchar_t MovesTakenDisplay[256];
	static wchar_t Time[256];
	//Convert the int values to wchar_t
	wsprintf(ScoreDisplay, L"SCORE: %d", Score);
	wsprintf(MovesTakenDisplay, L"MOVES: %d", MovesTaken);
	wsprintf(Time, L"TIME: %d", TimeInSeconds);
	
	//Clear BB
	m_pBackBuffer->Clear();

	//Foundation
	m_pFoundation->Draw();

	//Do not do this if game is initializing (Tableau animation)
	if(!m_bInitializing)
	{
		//Time Since New Game
		if(m_pTime->CurrentTime() - LastTime >1){
			TimeInSeconds++;
			LastTime = static_cast<int>(m_pTime->CurrentTime());
		}

		//Display text for Time / Score / Moves Taken
		SetBkMode(m_pBackBuffer->getDC(),TRANSPARENT);
		CheckLength(Score, 7);
		TextOut(m_pBackBuffer->getDC(), 20,20,ScoreDisplay, iDisplayLength);
		CheckLength(MovesTaken, 7);
		TextOut(m_pBackBuffer->getDC(), 150,20,MovesTakenDisplay, iDisplayLength);
		CheckLength(TimeInSeconds, 6);
		TextOut(m_pBackBuffer->getDC(), 250,20,Time, iDisplayLength);
		
		//Tableau draw
		m_pTableau->Draw();
		//Stock draw
		m_pStock->Draw();
		//UndoButton draw
		m_UndoButton.Draw(m_pBackBuffer->getDC());
	}

	//Grabbed cards draw
	auto grabbedCard = m_CardsGrabbed.begin();
	while(grabbedCard != m_CardsGrabbed.end())
	{
		(*grabbedCard)->Draw();
		grabbedCard++;
	}

	//Present everything to the window DC
	m_pBackBuffer->Present();

	//Animate the start
	if(m_bInitializing)
	{
		m_pTableau->InitializeAnimate(m_pStock);
		m_bInitializing = false;
	}
}

////////////////////////////
//Returns pointer to back buffer
///////////////////////////
BackBuffer* Game::GetBackBuffer()
{
	return m_pBackBuffer;
}

////////////////////////////
//Returns pointer to game instance
///////////////////////////
Game* Game::GetGameInstance() 
{    
	if(pGame)
	{
		return pGame;
	}
	else
	{
		return nullptr;
	}

}

////////////////////////////
//Returns handle to app instance
///////////////////////////
HINSTANCE Game::GetAppInstance()
{
	return m_hInstance;
}

////////////////////////////
//Returns handle to app window
///////////////////////////
HWND Game::GetWindow()
{
	return m_hWnd;
}

////////////////////////////
//Called whenever mouse is released by the player
//Used to determine where cards should go if player is holding on to cards currently
//Allows player to deposit cards into other piles
///////////////////////////
void Game::MouseRelease(Vector2 _clickPos)
{
	//Do everything only if player is holding onto cards
	if(!m_CardsGrabbed.empty())
	{
		//Isolate which pile was potentially clicked on based on position of mouse click
		CardPileGroup* _PileReleasedOn = IsolateCardPileGroup(_clickPos);

		//Null pointer means mouse click was not close to any piles
		if(_PileReleasedOn != nullptr)
		{
			int _iCardCount = m_CardsGrabbed.size();
			m_CardsMoved.clear();
			//Attempt to deposit cards into the pile released on
			if(_PileReleasedOn->AttemptCardDeposit(_clickPos, &m_CardsGrabbed, m_pListDepositedTo))
			{
				//Cards have been deposited so start recording move made
				auto _iter = m_pListDepositedTo->end();
				_iter--;
				for(int i = 0; i < _iCardCount; i++)
				{
					m_CardsMoved.push_back((*_iter));
					if(_iter == m_pListDepositedTo->begin())
					{
						break;
					}
					_iter--;
				}
				//Record the move made
				bool _bFlipTableauCard = true;
				if(!m_pListCardsGrabbedFrom->empty())
				{
					_bFlipTableauCard = !m_pListCardsGrabbedFrom->back()->IsFaceUp();
				}
				//Add move to the stack
				m_Moves.push(new Move(m_pListDepositedTo, &m_CardsMoved, _iCardCount, m_pListCardsGrabbedFrom, m_pPileGroupTakenFrom, _bFlipTableauCard));

				////SCORE BASED ON POS
				if(m_pListDepositedTo != m_pListCardsGrabbedFrom)
				{
					if( (_clickPos.y >= m_pFoundation->s_s_kiTop && _clickPos.y <= m_pFoundation->s_s_kiTop+96) && 
						(_clickPos.x >= m_pFoundation->s_s_kiLeft))
					{
						if(!m_Moves.top()->m_pCardsMoved->back()->GetFoundationPointsGiven())
						{
							m_Moves.top()->m_pCardsMoved->back()->SetFoundationPointsGiven(true);
							Score += 10;
							m_PointsGained.push(10);
						}
					}
					if((_clickPos.y >= m_pTableau->s_kiTop) &&(_clickPos.x >= m_pTableau->s_kiLeft))
					{
						if(!m_Moves.top()->m_pCardsMoved->back()->GetTableauPointsGiven())
						{
							m_Moves.top()->m_pCardsMoved->back()->SetTableauPointsGiven(true);
							Score += 5;
							m_PointsGained.push(5);
						}
					}
				}

				MovesTaken++;
				//Successfully added cards to new pile and removed from old
				m_pListCardsGrabbedFrom = nullptr;
				//Check whether tableau needs to flip a top card
				m_pTableau->FlipTopCards();
				m_pStock->ResupplyWaste();

				//Check GameWon Condition
				if(CheckGameWon())
				{
					if(TimeInSeconds > 30)
					{
						Score += TimeInSeconds/700000;
						m_bGameOver = true;
					}
				}
				//Toggle undo button to on if can undo move
				if(m_Moves.size() == 1)
				{
					m_UndoButton.ToggleCurrentDC();
				}
				return;
			}

		}
		
		//If piles missed by release or could not add to list,return to original list
		while(!m_CardsGrabbed.empty())
		{
			//add cards back to list grabbed from
			m_pListCardsGrabbedFrom->push_back(m_CardsGrabbed.front());

			//Clean up game containers
			m_CardsGrabbed.pop_front();

		}
		m_pTableau->ArrangeCardPositions();
		m_pFoundation->ArrangeCardPositions();
		m_pStock->ArrangeCardPositions();
		
		
		return;
	}
	else
	{
		//Undo button
		if(m_UndoButton.CheckCollision(_clickPos))
		{
			ActivateUndoButton();
			return;
		}
		else if(m_bButtonPushed)
		{
			m_bButtonPushed = false;
			m_UndoButton.ToggleCurrentDC();
		}
	}
}

////////////////////////////
//This is called when player clicks on undo button (waits for release)
///////////////////////////
void Game::ProcessUndoButton()
{
	if(m_Moves.empty())
	{
		return;
	}
	if(!m_bButtonPushed)
	{		
		m_bButtonPushed = true;
		m_UndoButton.ToggleCurrentDC();
	}
}

////////////////////////////
//This is called when player releases mouse on undo button after clicking on it
///////////////////////////
void Game::ActivateUndoButton()
{
	if(m_Moves.empty())
	{
		return;
	}

	if(m_bButtonPushed)
	{
		UndoMove();
		m_bButtonPushed = false;
		m_UndoButton.ToggleCurrentDC();
	}
}

////////////////////////////
//Called when player clicks on screen with mouse
///////////////////////////
void Game::MouseClick(Vector2 _clickPos)
{	
	//IF no cards in cardsgrabbed list
	if(m_CardsGrabbed.empty())
	{	
		////Check click on undo button
		if(m_UndoButton.CheckCollision(_clickPos))
		{
			ProcessUndoButton();
			return;
		}
		
		//Check position of click vs. all piles
		m_pPileGroupTakenFrom = IsolateCardPileGroup(_clickPos);

		if(m_pPileGroupTakenFrom != nullptr)
		{

			//Grab the cards and list taken from or get a null pointer
			m_pListCardsGrabbedFrom = m_pPileGroupTakenFrom->AttemptCardGrab(_clickPos, &m_CardsGrabbed);
			
			if(m_pListCardsGrabbedFrom != nullptr)
			{	
				//Determine distance between mouse click and top card
				m_CardMouseDist.x = _clickPos.x - m_CardsGrabbed.front()->GetPos().x;
				m_CardMouseDist.y = _clickPos.y - m_CardsGrabbed.front()->GetPos().y;
			}	
			else
			//Check if deck was clicked
			{

				if(typeid(*m_pPileGroupTakenFrom) == typeid(*m_pStock))
				{
					if(m_pStock->DeckClicked(_clickPos))
					{
						//Toggle undo button to on if can undo move
						if(m_Moves.empty())
						{
							m_UndoButton.ToggleCurrentDC();
						}
						//Add to moves taken if cards were dealt to waste
						if(!m_pStock->IsEmpty())
						{
							if(!m_pStock->GetStacks().back()->empty())
							{
								MovesTaken++;
							}
						}
						//Add move to stack which symbolizes clicking deck
						m_Moves.push(new Move(nullptr, nullptr,0, nullptr, nullptr));

						return;
					}
				}
			}
		}
		return;
	}	
}

////////////////////////////
//Mouse cards player is holding while he has not released mouse and grabbed some cards
///////////////////////////
void Game::MoveGrabbedCards(Vector2 _mousePos)
{	
	//Augment position so card dosn't flick to mouse pointer
	_mousePos.y = _mousePos.y - m_CardMouseDist.y;
	_mousePos.x = _mousePos.x - m_CardMouseDist.x;

	//Deduce space between cards //Could just use 20 from Tableau
	int _iYSpaceBetweenCards = 0;
	if(m_CardsGrabbed.size() > 1)
	{
		auto cardIter = m_CardsGrabbed.begin();
		cardIter++;
		
		_iYSpaceBetweenCards = abs((*cardIter)->GetPos().y - m_CardsGrabbed.front()->GetPos().y); 
	}
	
	//int _iMultiplier = 0;
	for(auto card = m_CardsGrabbed.begin(); card != m_CardsGrabbed.end(); card++)
	{
		
		//Split cards up vertically
		(*card)->SetPos(_mousePos.x, _mousePos.y);
		_mousePos.y += _iYSpaceBetweenCards;
	}
}

////////////////////////////
//Undo button activated
///////////////////////////
void Game::UndoMove()
{
	
	if(m_Moves.empty())
	{
		return;
	}
	
	//Toggle undo button to off if cant undo moves after this undo
	if(m_Moves.size() == 1)
	{
		m_UndoButton.ToggleCurrentDC();
	}


	Move* _move = m_Moves.top();

	//Reversal for clicking on deck
	if(_move->m_pCardsMoved == nullptr)
	{
		m_pStock->UndoDeckClick();
	}
	else
	{
		//Normal reversal
		int _iCardCount = _move->m_pCardsMoved->size();
		

		//Score keeping
		for(int i  = 0; i<7; i ++)
		{
			if(_move->m_pPileMovedTo == m_pTableau->GetStacks()[i]) 
			{
				if(_move->m_pCardsMoved->back()->GetTableauPointsGiven())
				{
					_move->m_pCardsMoved->back()->SetTableauPointsGiven(false);
					Score-= m_PointsGained.top();
					m_PointsGained.pop();
				}
				break;
			}
		}

		for(int i  = 0; i<4; i ++)
		{
			if(_move->m_pPileMovedTo == m_pFoundation->GetStacks()[i]) 
			{
				if(_move->m_pCardsMoved->back()->GetFoundationPointsGiven())
				{
					_move->m_pCardsMoved->back()->SetFoundationPointsGiven(false);
					Score-= m_PointsGained.top();
					m_PointsGained.pop();

				}
				break;
			}
		}

		_move->m_pPileGroupMovedFrom->UndoMove(_move);
		for(int i = 0; i < _iCardCount; i++)
		{
			//Remove cards from pile moved to
			_move->m_pPileMovedTo->pop_back();
			
		}
	}

	
	if(!m_Moves.empty())
	{
		delete m_Moves.top();
		m_Moves.pop();
	}
	

	m_pTableau->ArrangeCardPositions();
	m_pFoundation->ArrangeCardPositions();
	m_pStock->ArrangeCardPositions();
	
}

////////////////////////////
//Returns true if player has won the game
///////////////////////////
bool Game::CheckGameWon()
{
	if(!m_pTableau->IsEmpty() || !m_pStock->IsEmpty())
	{
		return false;
	}

	if(m_pFoundation->GameWon())
	{
		return true;
	}else
	{
		return false;
	}
}

////////////////////////////
//Returns state of the game
///////////////////////////
bool Game::GameOver()
{
	return m_bGameOver;
}

////////////////////////////
//Used at end of game to display game results
///////////////////////////
void Game::GetScoreTimeAndMoves(int& _iScore, int& _iTime, int& _iMoves)
{
	_iScore = Score;
	_iMoves = MovesTaken;
	_iTime = TimeInSeconds;
}

////////////////////////////
//Used to render
///////////////////////////
void Game::ExecuteOneFrame()
{
	if(!m_bGameOver)
	{
		m_pTime->dCurrent = m_pTime->CurrentTime();
		m_pTime->dDeltaTime = m_pTime->dCurrent - m_pTime->dLast;
			
	
		Draw();				

		m_pTime->dLast = m_pTime->dCurrent;
	}
}

////////////////////////////
//Used for initial animation
///////////////////////////
void Game::DrawNullSpaces()
{
	m_pFoundation->Draw();
	m_pStock->Draw();
}

////////////////////////////
//Used for score at end of game
///////////////////////////
void Game::CheckLength(int _i, int _addLength)
{
	if(_i < 10)
		iDisplayLength = 1 + _addLength;
	else if(_i >= 10 && _i < 100)
		iDisplayLength = 2 + _addLength;
	else if(_i >= 100 && _i < 1000)
		iDisplayLength = 3 + _addLength;
	else if(_i >= 1000 && _i < 10000)
		iDisplayLength = 4 + _addLength;
	else if(_i >= 10000 && _i < 100000)
		iDisplayLength = 5 + _addLength;
	else if(_i >= 100000)
		iDisplayLength = 6 + _addLength;
}